<?php
/**
 * class App_Model_Users
 *
 * @author kim, andrew, leo
 */
class App_Model_User extends App_Model_Abstract
{
    // table name
    protected $_name = 'users';

    //login table name
    protected $_login_table = 'users';

    /****************************** STATIC functions ******************************************/

    /**
     * Get user avatar photo source path
     *
     * @deprecated
     *
     * @param int $userId
     * @param string $size
     * @param array user - used to speed up results
     * @return string
     */
    static public function getAvatarSrc($userId = -1, $size = 'thumb', $user = NULL)
    {
        $userModel = new self();
        if ($user == NULL) {
            $user = $userModel->getUser($userId);
        }

        $photo = NULL;
        // if $user['photo_id'] == 0, then user doesn't have a photo
        if ($user['photo_id'] != 0) {
            // have to look it up because have to test for approval
            $query = "SELECT * FROM user_photos WHERE user_id = :user_id AND main = 1 AND status = 'APPROVED'";// ABC: AND status = 'APPROVED'";
            $photo = $userModel->fetchRow($query, array('user_id' => (int)$userId));
        }
        return App_Model_PhotoAbstract::getSrc($photo, 'user', $size);
    }

    /**
     * Get user avatar photo
     *
     * @todo - may need to integrate gravatar, see http://gravatar.com
     * @param int $userId
     * @param string $size
     * @param array user - used to speed up results
     * @return string
     */
    static public function getAvatar($userId = -1, $size = 'thumb', $user = NULL, $params = array('id'=>'avatar'))
    {
        $userModel = new self();
        if ($user == NULL) {
            $user = $userModel->getUser($userId);
        }
        $photo = NULL;
        if (!empty($user['photo_id'])) {
            $query = "SELECT * FROM user_photos WHERE id = :id AND user_id = :user_id";
            $photo = $userModel->fetchRow($query, array('id'=>$user['photo_id'], 'user_id' => $userId));
        }
        return App_Model_PhotoAbstract::getAvatar($photo, App_Model_PhotoAbstract::TYPE_USER, $size, $params, $user['gender']);
    }

    /**************************** USER DISPLAY functions *****************************************************/

    /**
     * given a user, returns what to display as their "username"
     *
     * andrew : please keep by reference - kim : please do not use reference in function params, 1st it's not safe, 2nd it's confused, especially in team work
     *
     * @todo - get display user name by current site and correct naming convention
     *
     * @param array $user - user info
     * @return string
     */
    static public function getDisplayUsername(&$user)
    {
        /*
        if (!isset($user['id'])) { return "MEMBERX"; } // use for debugging

        $site = 'dine';
        $name = '';
        // return users.firstname+users.lastname
        if ($site == 'dine') {
            if (isset($user['firstname'])) $name = ucwords($user['firstname']);
            if (isset($user['lastname']) && strlen($user['lastname'])>0) {
                $name .= ' ' . strtoupper(substr($user['lastname'], 0, 1)) . '.';
            }
            if (strlen($name)<4) { $name='m'.$user['id']; }
            return $name;
        } else {
            // return users.username
            return $user['username'];
        }
        */

    	$name = '';
        if (isset($user['firstname'])) {
        	if (strlen($user['firstname']) > 16) $name .= ucwords(substr($user['firstname'], 0, 16));
        	else $name .= ucwords($user['firstname']);
        }
        if (isset($user['lastname']) && strlen($user['lastname']) > 0) {
            $name .= ' ' . strtoupper(substr($user['lastname'], 0, 1)) . '.';
        }

        if (empty($name) && !empty($user['username'])) {
        	$name = $user['username'];
        }

        if (!empty($name)) return $name;
        else return $name='m'.$user['id'];
    }

    /****************************** AUTH checking functions ******************************************/

    /**
     * Check if user can edit item
     *
     * @todo should depend on ACL in future
     * @param array $user
     * @param array $item
     * @return boolean
     */
    static public function canEdit($user, $item)
    {
        return self::isOwner($user, $item);
    }

    /**
     * Check if user is owner of the item
     *
     * @todo should depend on ACL in future
     * @param array $user
     * @param array $item
     * @return boolean
     */
    static public function isOwner($user, $item)
    {
        if (isset($user['id']) and isset($item['user_id'])) {
            return $user['id'] == $item['user_id'];
        }
        return false;
    }

    /**
     * check current client is viewing his own profile or not
     * 1. check user is logged in or not
     * 2. check whether user_id = profile_id
     *
     * @param profile(user) id
     * @return boolean
     */
    static public function isViewingOwn($profileUserId)
    {
        $myUserId = self::getStoredMyUserId();
        return ($myUserId == $profileUserId);
    }

    /**
     * get auth instance
     * this is the auth object that handle user authentication, basically login for now
     * it use a independent namespace "site_dine" for session/cookie, to avoid conflict from other sites
     *
     * @return Zend_Auth
     */
    static protected function getAuth()
    {
        $auth = Zend_Auth::getInstance();
        $namespace = 'ns' . md5(APPLICATION_PATH);
        $auth->setStorage(new Zend_Auth_Storage_Session($namespace));
        return $auth;
    }

    /**
     * check if user is loged in
     *
     * @return boolean
     */
    static public function isLoggedIn ()
    {
        return self::getAuth()->hasIdentity() ? true : false;
    }

    /**
     * user logout function
     *
     * @return null
     */
    static public function logout()
    {
        self::getAuth()->clearIdentity();
    }

    /**
     * set stored my_user info
     * this function check whether user is logged in
     *
     * return array|false
     */
    static public function setStoredMyUser()
    {
        $result = false;
        if (self::isLoggedIn()) {
            $email = self::getAuth()->getIdentity();
            $userModel = new self();
            if ($myUser = $userModel->getUser($email)) {
	            Zend_Registry::set('my_user', $myUser);
                $result = $myUser;
            }
        }
        return $result;
    }

    /**
     * get stored my_user info
     * this function check whether user is logged in
     *
     * @todo - redirect to login page if failure
     *
     * return array|false
     */
    static public function getStoredMyUser()
    {
        if (self::isLoggedIn() and Zend_Registry::isRegistered('my_user')) {
            $myUser = Zend_Registry::get('my_user');
            return $myUser;
        }
        return false;
    }

    /**
     * get stored my_user info
     * this function check whether user is logged in
     * redirect to login page if failure
     *
     * return int|false
     */
    static public function getStoredMyUserId()
    {
        if ($myUser = self::getStoredMyUser()) {
            return $myUser['id'];
        }
        return false;
    }

    /****************************** PASSWORD functions ******************************************/

    /**
     * create user password in hash
     *
     * @param string $password - original password string
     * @return string - hashed password
     */
    static public function createPasswordHash($password)
    {
        return Zend_Crypt::hash('md5', $password);
    }

    /**
     * create random user password string
     *
     * @param int $length - password length
     * @return string
     */
    static public function createPassword($length = 3)
    {
        $chars = "ABCDEFGHJKLMNPQRSTUVWXYZ";
        $numbers = '23456789';
        $i = 0;
        $password = '';
        while ($i < $length) {
            $password .= $chars{mt_rand(0, strlen($chars) - 1)};
            $password .= $numbers{mt_rand(0, strlen($numbers) - 1)};
            $i++;
        }
        return $password;
    }

    /**
     * get password by login id
     *
     * @todo - may need to remove in future, because we may use hash password instead
     *
     * @param $loginId
     * @return string
     */
    public function getPassword($loginId)
    {
        $query = "SELECT password FROM " . $this->_login_table . " WHERE id = :id";
        $result = $this->getTable()->getAdapter()->fetchRow($query, array('id' => $loginId));
        return $result['password'];
    }

    /****************************** LOGIN/REGISTRATION functions ******************************************/

    /**
     * save the login number per day
     * the reason why we have to pass email as the param is because :
     * we couldn't know the user's id before user login
     *
     * @todo - maybe can use save(), update(), insert() instead, but that will make confused than using pure sql
     * @param string $email
     * @return int|false
     */
    protected function _saveLoginPerDay($email)
    {
        $result = false;
        $userId = 0;
        $today = date('Y:m:d');
        if (preg_match('/\@/',$email))
            $userId = $this->getUserIdByLoginEmail($email);
        else {
            $query = "SELECT login_id FROM users WHERE username = :username";
            $userId = $this->fetchOne($query, array('username' => $email));
        }
        if($userId) {
            $sql = "SELECT num_logins FROM login_per_day WHERE day = :day AND user_id = :user_id";
                $num_logins = $this->fetchOne($sql, array('day' => $today, 'user_id' => $userId));

                $db = $this->getTable()->getAdapter();
                if ($num_logins) {
                    $sql = "UPDATE login_per_day SET num_logins=num_logins+1 WHERE day = :day AND user_id = :user_id";
                    $result = $db->query($sql, array('day' => $today, 'user_id' => $userId));
                } else {
                    $login = array('day' => $today, 'user_id' => $userId, 'num_logins' => 1, 'created_at' => date('Y:m:d h:i:s'));
                    $sql = "INSERT INTO login_per_day (day, user_id, num_logins, created_at) VALUES (:day, :user_id, :num_logins, :created_at)";
                    $result = $db->query($sql, $login);
                }
        }
        return $result;
    }

    /**
     * save login attempt when fail
     * the reason why we have to pass email as the param is because :
     * we couldn't know the user's id before user login
     *
     * @param string $email
     * @return int|false
     */
    protected function _saveLoginAttempt($email)
    {
        $result = false;
        if (preg_match('/\@/',$email)) {
            $login = array('username' => '', 'email' => $email, 'ip' => App_Model_Geo::getClientIp(), 'created_at' => date('Y:m:d h:i:s'));
        }
        else {
            $login = array('username' => $email, 'email' => '', 'ip' => App_Model_Geo::getClientIp(), 'created_at' => date('Y:m:d h:i:s'));
        }

        $db = $this->getTable()->getAdapter();

        $sql = "INSERT INTO login_attempts (username, email, ip, created_at) VALUES (:username, :email, :ip, :created_at)";
        $result = $db->query($sql, $login);
        return $result;
    }

    /**
     * check email if exist in database
     *
     * @param string $email
     * @return boolean - return false if not exist, otherwise true
     */
    public function checkEmailExists($email = '')
    {
        $query = "SELECT login_id FROM users WHERE email = :email";
        $loginId = $this->fetchOne($query, array('email' => $email));
        if ($loginId) {
            $query = "SELECT email FROM " . $this->_login_table . " WHERE id = :id";
            $emailInDb = $this->fetchOne($query, array('id' => $loginId));
            if ($emailInDb and ($emailInDb == $email)) {
                return true;
            }
        }
        return false;
    }

    /**
     * get authentication result
     * auth will handle the validation and session things
     *
     * we will save id instead of email for identity
     *
     * @todo may add MD5() in the future
     * @param array $data - user login data
     * @return true|string - return true if logged in, error message on failure
     */
    public function getLoginResult($data)
    {
        $auth = self::getAuth();
        $email = $data['email_username'];
        $username = false;
        //username
        if (!preg_match('/\@/',$email)) {
            $query = "SELECT email FROM users WHERE username = :username";
            $username = $email;
            $email = $this->fetchOne($query, array('username' => $username));
        }
        //if email not found by username, return error message.
        if (!preg_match('/\@/',$email)) {
            //$this->_saveLoginAttempt($username);
            //return 'Username or email address not found!';
        }
        $adapter = new Zend_Auth_Adapter_DbTable($this->getTable()->getAdapter(), $this->_login_table, 'email', 'password');
        $adapter->setIdentity($email);
        $adapter->setCredential($data['password']);
        $result = $auth->authenticate($adapter);

        //if($result->getCode() == Zend_Auth_Result::SUCCESS || $result->getCode() == Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS) {
        if ($result->isValid()) {
            $identity = $email;
            $auth->getStorage()->write($identity);
            //$this->_saveLoginPerDay(($username == false)?$email:$username);
            return true;
        } else {
            //$this->_saveLoginAttempt(($username == false)?$email:$username);

            switch ($result->getCode()) {
                case Zend_Auth_Result::SUCCESS:
                    $message = 'Login success';
                    break;
                case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND:
                    $message = 'Username or email address not found';
                    break;
                case Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS:
                    $message = 'Unknown username or email address';
                    break;
                case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID:
                    $message = 'Please provide a valid password';
                    break;
                case Zend_Auth_Result::FAILURE_UNCATEGORIZED:
                    $message = 'Uncategorized error!';
                    break;
                case Zend_Auth_Result::FAILURE:
                    $message = 'Login fail';
                    break;
                default:
                    $message = implode("\n", $result->getMessages());
                    break;
            }
            return $message;
        }
    }

    /**
     * save user
     *
     * @param array $data - user data
     * @return int
     */
    public function saveUser($data)
    {
        $result = false;
        if (App_Model_Mail::isEmail($data['email'])) {
            $user = array();
            list($city, $state) = explode(',', $this->_g($data, 'city_state'));

            $user['city'] = trim($city);
            $user['state'] = trim($state);
            $user['country'] = $this->_g($data, 'country');
            $user['zip'] = $this->_g($data, 'zip');


            $geo = new App_Model_Geo();

            //auto set city, state by zip if us.
            if($user['zip'] && $user['country'] == 'United States' && empty($user['city'])) {
                    $zips = $geo->getCityStateByZip($user['zip']);
                    if($zips && $zips['city']) {
                        $user['city'] = $zips['city'];
                        $user['state'] = $zips['state'];
                    }
             }

             // get region
            $myRegion = $geo->getLocalInfoFromCSC($user['country'], $user['state'], $user['city']);
            if (!$myRegion['id']) {
                //throw new exception('User need a local!');
            }

            // get location_id
            $myLocation = $geo->getLocationInfoFromCSC($user['country'], $user['state'], $user['city']);
            if (!$myLocation['fid']) {
                //throw new exception('User need a local!');
            }

            // get lat/lon
            $latlon = $geo->getLatLonFromCSCZ($user['country'], $user['state'], $user['city'], $user['zip']);
            if (!$latlon) {
                //throw new exception('User need lat/lon!');
            }

            $user['latitude']       = $this->_g($latlon, 'lat');
            $user['longitude']      = $this->_g($latlon, 'lon');
            $user['status']         = 'PENDING';
            $user['local_id']       = $this->_g($myRegion, 'id');
            $user['local_id_list']  = $this->_g($myRegion, 'parent_id_list');
            $user['location_id']    = $this->_g($myLocation, 'fid');

            $user['login_id']   = $this->_g($data, 'login_id');
            $user['email']      = $this->_g($data, 'email');
            $user['username']   = $this->_g($data, 'username', $data['email']);
            $user['firstname']  = $this->_g($data, 'firstname');
            $user['lastname']   = $this->_g($data, 'lastname');
            $user['gender']     = $this->_g($data, 'gender');

            $year = $this->_g($data, 'year', '0000');
            $month = $this->_g($data, 'month', '00');
            if ($month < 10) $month = '0' . $month;
            $day = $this->_g($data, 'day', '00');
            if ($day < 10) $day = '0' . $day;
            $user['birthdate']  = sprintf("%04d-%02d-%02d", $year, $month, $day);

            $user['mailing_list'] = $this->_g($data, 'mailing_list') ? 1 : 0;
            $user['reg_ip']     = App_Model_Geo::getClientIp();
            $user['created_at'] = date('Y:m:d h:i:s'); //ABC: this is dangerous if 1 webserver has bad time
            //var_dump($user);die; // for testing

            $result = $this->getTable()->insert($user);
        }
        return $result;
    }

    /**
     * send register email
     *
     * @param int $userId
     * @return boolean
     */
    public function sendRegisterEmail($userId)
    {
        $result = false;

        // try to get the user info
        if ($userId) {
            $userInfo = $this->getUser($userId);
            $userInfo['password'] = $this->getPassword($userInfo['login_id']);

            if ($userInfo) {
                // get the static view object
                $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
                $view = $viewRenderer->view;

                // send registration email
                $from = $view->T('EMAIL_TEMPLATES_REGISTER_SENDFROM');
                $subject = $userInfo['username'] . ', ' . $view->T('EMAIL_TEMPLATES_REGISTER_SUBJECT');
                $body = $view->partial('user/tpl-register.phtml', array('user' => $userInfo));
                $mail = new App_Model_Mail();
                $isSent = $mail->send($userInfo['email'], $subject, $body, $from);

                // send explanation email
                if ($isSent) {
                    $subject2 = $view->T('EMAIL_TEMPLATES_REGISTER_EXPLANATION_SUBJECT') . ', ' . $userInfo['username'];
                    $body2 = $view->partial('user/tpl-register-explanation.phtml', array('user' => $userInfo));
                    if ($mail->send($userInfo['email'], $subject2, $body2, $from)) {
                        //$this->_helper->redirector('login', 'user', null, array('email' => $userInfo['email']));
                        //$this->_helper->json->sendJson(array('result' => $userId, 'success' => true));
                        $result = true;
                    }
                }
            } else {
                throw new exception('Unknown user : ' . $userId);
            }
        } else {
            throw new exception('User id needed for register email sending.');
        }

        return $result;
    }

    /**
     * get register result
     *
     * @todo send email of password, set is_active to 0
     *
     * @param array $data
     * @return new user id
     */
    public function getRegisterResult($data)
    {
        $result = false;
        // if email not exist
        if (false == $this->checkEmailExists($data['email'])) {
            //$location_input_type = $data['location_input_type'];

            $userId = $this->saveUser($data);
            //var_dump($userId);
            if ($userId) {
                //save data to table logins
                if (empty($data['password_new'])) {
	                $password = self::createPassword();
                } else {
                	$password = $data['password_new'];
                }
                $login = array(
                    'email' => $data['email'],
                    'password' => $password,
                    'password_md5' => self::createPasswordHash($password),
                );

                $db = $this->getTable()->getAdapter();
                $db->insert($this->_login_table, $login);
                $loginId = $db->lastInsertId();
                //var_dump($loginId);

                if ($loginId) {
                    $db->update('users', array('login_id' => $loginId), array('id = ?' => $userId));

                    $this->updateUserFavoriteTypes($userId, $data['type_favorites']);
                    $this->updateUserText($userId, 'bio', $data['bio']);

                    // return user id as result
                    $result = $userId;
                }
            }
        }
        return $result;
    }

    /****************************** RESET PASSWORD functions ******************************************/

    /**
     * send reset password email
     *
     * @param int $loginId - the login account's id
     * @return boolean
     */
    public function sendResetPwdEmail($loginId)
    {
        $result = false;

        // try to get the login account's info
        if ($loginId) {
            if ($loginAccountInfo = $this->fetchRow("SELECT * FROM " . $this->_login_table . " WHERE id = :id", array('id' => $loginId))) {

                // get the static view object
                $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
                $view = $viewRenderer->view;

                // format the email by template and account's info
                $subject = $view->T('EMAIL_TEMPLATES_RESET_PWD_SUBJECT');
                $from = $view->T('EMAIL_TEMPLATES_RESET_PWD_SENDFROM');
                $body = $view->partial('user/tpl-reset-pwd.phtml', array('loginAccountInfo' => $loginAccountInfo));

                // try to send the email
                $mail = new App_Model_Mail();
                if ($mail->send($loginAccountInfo['email'], $subject, $body, $from)) {
                    $result = true;
                } else {
                    throw new exception('Unable to send reset password email of this account : ' . $loginId . ' - ' . $loginAccountInfo['email']);
                }
            } else {
                throw new exception('Unknown login account : ' . $loginId);
            }
        } else {
            throw new exception('Login id is needed for reset password email sending.');
        }

        return $result;
    }

    /**
     *
     * @param unknown_type $loginId
     */
    public function sendFindUsernameEmail($email)
    {
        $result = false;

        // try to get the login account's info
        if ($email) {
            $userInfo = $this->fetchRow("SELECT * FROM users WHERE email = :email", array('email' => $email));
            if ($userInfo) {
                // get the static view object
                $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
                $view = $viewRenderer->view;

                // format the email by template and account's info
                $subject = $view->T('EMAIL_TEMPLATES_FIND_USERNAME_SUBJECT');
                $from = $view->T('EMAIL_TEMPLATES_FIND_USERNAME_SENDFROM');
                $body = $view->partial('user/tpl-find-username.phtml', array('user' => $userInfo));

                // try to send the email
                $mail = new App_Model_Mail();
                if ($mail->send($userInfo['email'], $subject, $body, $from)) {
                    $result = true;
                } else {
                    throw new exception('Unable to send find username email of this account : ' . $userInfo['email']);
                }
            } else {
                throw new exception('Unknown email : ' . $email);
            }
        } else {
            throw new exception('email is needed for find username email sending.');
        }

        return $result;
    }

    /**
     * send hash key for password reset
     *
     * we use email as identity because :
     * both loggedin/unloggedin user could provide their email when try to reset password
     *
     * will generate a hash key for user verify
     * @see - logins.confirm_pwd_hash
     *
     * @todo add this exception to log file.
     *
     * @param string $email
     * @param string $key
     * @return boolean|int - "false" if invalid email, "int" the affected login account's user id
     */
    public function setPasswordKey($email)
    {
        $result = false;
        if (App_Model_Mail::isEmail($email)) {
            if ($this->checkEmailExists($email)) {
                $confirmPwdHash = Zend_Crypt::hash('md5', time());
                $update = "UPDATE " . $this->_login_table . " SET confirm_pwd_hash = :confirm_pwd_hash WHERE email = :email";
                $db = $this->getTable()->getAdapter();
                $affected = $db->query($update, array('confirm_pwd_hash' => $confirmPwdHash , 'email' => $email));
                if ($affected->rowCount()) {
                    $result = $this->fetchOne("SELECT id FROM " . $this->_login_table . " WHERE email = :email", array('email' => $email));
                }
            }
        }
        return $result;
    }

    /**
     * get reset password result
     *
     * @todo - any exception?
     *
     * @param array $data
     * @return boolean
     */
    public function getResetPasswordResult($data)
    {
        $result = false;

        // email validate
        if (isset($data['email']) and App_Model_Mail::isEmail($data['email'])) {

            // check email exists
            if ($this->checkEmailExists($data['email'])) {
                $query = "SELECT email FROM " . $this->_login_table . " WHERE email = :email AND confirm_pwd_hash = :confirm_pwd_hash ";
                $r = $this->fetchCol($query, array('email' => $data['email'], 'confirm_pwd_hash' => $data['key']));

                // generate new password confirm hash
                if (count($r)) {
                    $login = array(
                        'password'          => $data['password_new'],
                        'password_md5'      => self::createPasswordHash($data['password_new']),
                        'email'             => $data['email'],
                        'confirm_pwd_hash'  => $data['key'],
                    );
                    $query = "UPDATE " . $this->_login_table . " SET password = :password, password_md5 = :password_md5, confirm_pwd_hash = '' "
                           . "WHERE email = :email AND confirm_pwd_hash = :confirm_pwd_hash";
                    $db = $this->getTable()->getAdapter();
                    $affected = $db->query($query, $login);
                    if ($affected->rowCount()) {
                        $result = true;
                    }
                }
            }
        }

        return $result;
    }

    /**************************** DEFINE INFO FOR USER functions *********************************************************/

    /**
     *  returns the user's region using their stored info or IP
     *
     **/
    public function getRegion($params = NULL) {
      $defaultLocalId = $this->_g($params,'default_local_id',192); //189 NorthAm, 192=USA, 11129=Seattle

      $userModel = new App_Model_User();
      $geo = new App_Model_Geo();

      if ($userId = App_Model_User::getStoredMyUserId()) {
        // load up a copy of the user array...
        $user = $userModel->getUser($userId);
        $localId = $user['local_id'];
        $region = $geo->getLocalInfo($localId);
      } else {
        $region = $geo->getLocalInfoFromIP();
        $localId = $region['id'];
      }

      // if the region is outside US/Canada, put them in the default region for site
      //$localIdList = $this->_g($region, 'parent_id_list');
      $localIdList = $region['parent_id_list'];
      if (!preg_match('/^1,189,192,/', $localIdList) &&
          !preg_match('/^1,189,190,/', $localIdList)) {
        $localId = $defaultLocalId;
        $region = $geo->getLocalInfo($localId);
      }

      return $region;
    }

    /**************************** UPDATE PROFILE functions *********************************************************/

    /**
     * do profile update and return update feedback
     * it does update instead of insert, which is different from saveUser()
     *
     * @param array $data
     * @return boolean
     */
    public function getProfileUpdateResult($data)
    {
        list($city, $state) = explode(',', $data['city_state']);

        $userData = array(
            'firstname' => $data['firstname'],
            'lastname' => $data['lastname'],
            'gender' => $data['gender'],
            'birthdate' => $data['birthdate'],
            'country' => $data['country'],
            'zip' => $data['zip'],
            'city' => trim($city),
            'state' => trim($state),
        );


        //auto set city, state by zip if us.
        if($userData['zip'] && $userData['country'] == 'United States' && empty($userData['city'])) {
                 $geo = new App_Model_Geo();
                $zips = $geo->getCityStateByZip($userData['zip']);
                if($zips && $zips['city']) {
                    $userData['city'] = $zips['city'];
                    $userData['state'] = $zips['state'];
                }
         }

        // refresh user favorite place types
        $this->updateUserFavoriteTypes($data['user_id'], $data['type_favorites']);
        // update user info
        $affectedRows = $this->getTable()->update($userData, array('id = ?' => $data['user_id']));
        return $affectedRows ? true : false;
    }

    /**************************** USER ACTIONS functions *********************************************************/

    /** record the user action **/
    public function updateUserActions($userId, $action, $dataId=0, $toUserId=0)
    {
      $db = $this->getTable()->getAdapter();
      $user = $this->getUser($userId);
      $localIdList = $user['local_id_list'];
      $gender = $user['gender'];

      $db->query("INSERT INTO user_actions (user_id, to_user_id, action, data_id, local_id_list, gender, created_at) "
		 . "VALUES ('$userId', '$toUserId', '$action', '$dataId', '$localIdList', '$gender', NOW()) "
		 . "ON DUPLICATE KEY UPDATE created_at = NOW()");
    }

    /**************************** PROFILE SETTINGS functions *********************************************************/

    /**
     * update user text
     *
     * @param int $userId
     * @param string $key - text type
     * @param string $value - text content
     * @return false|int
     */
    public function updateUserText($userId, $key, $value)
    {
        $result = false;
        if ($userId and $key and $value) {
            $db = $this->getTable()->getAdapter();

            $result = $db->query("INSERT INTO user_text_pending (user_id, name, value, created_at) "
                     . "VALUES (:user_id, :name, :value, NOW()) "
                     . "ON DUPLICATE KEY UPDATE value = :value, created_at = NOW()",
                     array('user_id' => $userId, 'name' => $key, 'value' => $value));

            /** ABC: looking to use bind vars here
            $result = $db->query("INSERT INTO user_text_pending (user_id, name, value, created_at) VALUES ('$userId', ':key', ':value', '$createdAt') ON DUPLICATE KEY UPDATE :key = ':value', created_at = NOW()",
            array('key' => $key, 'value' => $value));
            ***/

        }
        return $result;
    }

    /**
     * update user's email address
     * pay attention to the email address which is the old email of user
     *
     * @todo - tool for sending an email, tool for exception log
     *
     * @param int $userId
     * @param string $email
     * @return boolean
     */
    public function updateEmailAddress($userId, $email)
    {
        $result = false;
        if (App_Model_Mail::isEmail($email)) {
            // get login id
            $logins = $this->fetchRow("SELECT email, login_id FROM users WHERE id = '$userId'");
            if (!empty($logins)) {
                $db = $this->getTable()->getAdapter();

                $loginId = $logins['login_id'];
                $query = "UPDATE " . $this->_login_table . " SET email = :email, confirmed_date = NULL WHERE id = :login_id";
                $updateLoginsResult = $db->query($query, array('email' => $email, 'login_id' => $loginId));

                $query = "UPDATE users SET email = :email WHERE id = :user_id";
                $updateUsersResult = $db->query($query, array('email' => $email, 'user_id' => $userId));

                // send confirm email
                $mail = new App_Model_Mail();
                $subject = "Dine.com - New email address confirmation";
                $body = "Your new email address : " . $email;
                try {
                    $result = $mail->send($logins['email'], $subject, $body);
                } catch (Exception $e) {
                    App_Model_Log::write($e);
                }
            }
        }
        return $result;
    }

    /**
     * update user's password
     *
     * @todo - need verify password validation checking
     *
     * @param int $userId
     * @param string $password
     * @return boolean
     */
    public function updatePassword($userId, $password)
    {
        $result = false;
        $password = trim($password);
        if (Zend_Validate::is($password, 'Regex', array('/^[a-zA-Z0-9]+$/'))) {
            $db = $this->getTable()->getAdapter();
            $query = "UPDATE " . $this->_login_table . " SET password = :password, password_md5 = :password_md5 "
                   . "WHERE " . $this->_login_table . ".id = (SELECT login_id FROM users WHERE users.id = :user_id)";
            $result = $db->query($query, array('password' => $password, 'password_md5' => self::createPasswordHash($password), 'user_id' => $userId));
        }
        return $result;
    }

    /**
     * update user's profile_on option
     *
     * @param int $userId
     * @param int $profileOnOption
     * @return boolean
     */
    public function updateProfileOption($userId, $profileOnOption)
    {
        $result = false;
        if ($userId) {
            $option = $profileOnOption ? 1 : 0;
            $db = $this->getTable()->getAdapter();
            $query = "UPDATE users SET profile_on = :profile_on WHERE id = :user_id";
            $result = $db->query($query, array('profile_on' => $option, 'user_id' => $userId));
        }
        return $result;
    }

    /**
     * update user's profile_on option
     *
     * @param int $userId
     * @param int $profileOnOption
     * @return boolean
     */
    public function updateMailingList($userId, $mailingList)
    {
        $result = false;
        if ($userId) {
            $option = $mailingList ? 1 : 0;
            $db = $this->getTable()->getAdapter();
            $query = "UPDATE users SET mailing_list = :mailing_list WHERE id = :user_id";
            $result = $db->query($query, array('mailing_list' => $option, 'user_id' => $userId));
        }
        return $result;
    }

    /**
     * update user  'want_age_min', 'want_age_max', 'want_genders'
     *
     * @param int $userId
     * @param array $data
     * @return boolean
     */
    public function updateIdealMemberOption($userId, $data) {
        $result = false;
        if($userId) {
            $db = $this->getTable()->getAdapter();
            $sql = "update users set want_age_min =:min, want_age_max = :max, "
                   ."want_genders =:genders where id =:user_id";

            $result = $db->query($sql, array('min' => $data['want_age_min'],
                'max' => $data['want_age_max'], 'genders' => $data['want_genders'], 'user_id' => $userId));
        }
        return $result;
    }

    /**
     * update user settings 'browse_anonymously', 'no_photo_votes', 'no_photo_comments'
     *
     * @param int $userId
     * @param array $data
     * @return boolean
     */
    public function updateSettings($userId, $data)
    {
        $result = false;
        if ($userId and !empty($data)) {
            $sql = "select id from user_settings where user_id=:user_id";
            $id = $this->fetchOne($sql, array('user_id' => $userId));

            $db = $this->getTable()->getAdapter();
            if(!$id) {
                $db->insert('user_settings', array('user_id' => $userId));
                $id = $db->lastInsertId();
            }

            if (isset($data['filter_messages_not_ideal'])) {
                $db->query("UPDATE user_settings SET filter_messages_not_ideal = :value WHERE id = :id",
                                            array('value' => $data['filter_messages_not_ideal'] ? 1 : 0, 'id' => $id)
            );
            }
            if (isset($data['filter_messages_not_friend'])) {
                $db->query("UPDATE user_settings SET filter_messages_not_friend = :value WHERE id = :id",
                                            array('value' => $data['filter_messages_not_friend'] ? 1 : 0, 'id' => $id)
            );
            }
            if (isset($data['browse_anonymously'])) {
                $db->query("UPDATE user_settings SET browse_anonymously = :value WHERE id = :id",
                                            array('value' => $data['browse_anonymously'] ? 1 : 0, 'id' => $id)
            );
            }
            if (isset($data['no_photo_voting'])) {
                $db->query("UPDATE user_settings SET no_photo_voting = :value WHERE id = :id",
                                array('value' => $data['no_photo_voting'] ? 1 : 0, 'id' => $id)
            );
            }
            if (isset($data['no_photo_comments'])) {
                $db->query("UPDATE user_settings SET no_photo_comments = :value WHERE id = :id",
                        array('value' => $data['no_photo_comments'] ? 1 : 0, 'id' => $id));
            }
            $result = true;
        }
        return $result;
    }

    /********************* FAVORITE TYPES/PLACES functions ***********************************************/

    /**
     * update user's favorite place types
     *
     * @todo to update what really need to be updated
     *
     * @param int $userId
     * @param array $typeIds
     * @return null
     */
    public function updateUserFavoriteTypes($userId, $typeIds)
    {
        $result = false;
        if (!empty($userId) and !empty($typeIds)) {
            $db = $this->getTable()->getAdapter();
            $db->delete('user_type_favorites', $db->quoteInto('user_id = ?', $userId));
            foreach ($typeIds as $tid) {
                $data[] = "(NULL, $userId, $tid)";
            }
            $result = $db->query("INSERT INTO user_type_favorites VALUES " . implode(',', $data));
        }
        return $result;
    }

    /**
     * get user favorite place type ids by user id
     * this function can be used to set default values of forms
     * for example profile edit form
     *
     * @param int $userId
     * @return array
     */
    public function getFavoriteTypeIdsByUserId($userId)
    {
        $query = "SELECT type_id FROM user_type_favorites WHERE user_id = :user_id";
        $result = $this->fetchCol($query, array('user_id' => $userId));
        return $result;
    }

    /**
     * returns an array of favorite type names given a user id
     *
     * @param int $userId
     * @return array
     */
    public function getFavoriteTypesByUserId($userId)
    {
        $query = "SELECT name FROM user_type_favorites utf, types WHERE utf.type_id = types.id AND utf.user_id = :user_id ORDER BY types.name";
        $result = $this->fetchAll($query, array('user_id' => $userId));
        return $result;
    }

    /************************** BLOCK/FOLLOWER/FRIEND functions *****************************************/

    // return 1 if $viewerUserId is blocked by $blockerUserId
    public function isBlocked($blockerUserId, $viewerUserId = false) {
      if (!$viewerUserId) {
        $viewerUserId = App_Model_User::getStoredMyUserId();
      }
      $test = $this->fetchRow("SELECT * FROM user_blocks WHERE user_id='$viewerUserId' AND blocked_user_id='$blockerUserId'");
      return isset($test['id']) ? 1 : 0;
    }

    // return 1 if $followed is followed by $follower
    public function isFollowed($followed, $follower = NULL) {
        if($follower == NULL) {
           $follower = App_Model_User::getStoredMyUserId();
        }
        $is_followed = $this->fetchOne("SELECT COUNT(id) as f FROM followers WHERE follower_id = :follower_id and followed_id = :followed_id",
               array('follower_id' => $follower, 'followed_id' => $followed));
        return ($is_followed > 0)? 1:0;
    }

    // return 1 if $viewerUserId is friended by $frienderUserId
    public function isFriend($frienderUserId, $viewerUserId = NULL) {
      if ($viewerUserId == NULL) {
        $viewerUserId = App_Model_User::getStoredMyUserId();
      }
      $test = $this->fetchRow("SELECT * FROM friends WHERE from_user_id='$frienderUserId' AND to_user_id='$viewerUserId'");
      return isset($test['id']) ? 1 : 0;
    }

    /**
     * block user
     * @param int $blockedUserId
     * @param int $userId
     * @return bool
     */
    public function blockUser($blockedUserId, $userId = NULL) {
       if($userId == NULL) {
           $userId = App_Model_User::getStoredMyUserId();
       }
       $result = false;
       $isBlocked = $this->isBlocked($blockedUserId, $userId);
       if($isBlocked) {
           $result = true;
       }
       else {
           $sql = "INSERT INTO user_blocks (blocked_user_id, user_id, created_at) VALUES (:blocked_user_id, :user_id, NOW())";
           $result = $this->query($sql, array('blocked_user_id' => $blockedUserId, 'user_id' => $userId));
       }
       return $result;
    }

    public function unblockUser($blockedUserId, $userId = NULL) {
       if($userId == NULL) {
           $userId = App_Model_User::getStoredMyUserId();
       }
       $result = false;
       $isBlocked = $this->isBlocked($blockedUserId, $userId);
       if(!$isBlocked) {
           $result = true;
       }
       else {
           $sql = "DELETE FROM user_blocks where blocked_user_id =:blocked_user_id and user_id=:user_id";
           $result = $this->query($sql, array('blocked_user_id' => $blockedUserId, 'user_id' => $userId));
       }
       return $result;
    }

    /**
     * follow user
     *
     * @param int $followed
     * @return bool
     */
    public function followUser($followed, $follower = NULL) {
       if($follower == NULL) {
           $follower = App_Model_User::getStoredMyUserId();
       }
       $result = false;
       $isFollowed = $this->isFollowed($followed, $follower);

       if($isFollowed) {
           $result =true;
       }
       else {
           $sql = "INSERT INTO followers (follower_id, followed_id, created_at) VALUES (:follower_id, :followed_id, NOW())";
           $result = $this->query($sql, array('follower_id' => $follower, 'followed_id' => $followed));
       }
       return $result;
    }

    /**
     * unfollow user
     * @param int $followed
     * @param int $follower
     * @return bool
     */
    public function unfollowUser($followed, $follower = NULL) {
       if($follower == NULL) {
           $follower = App_Model_User::getStoredMyUserId();
       }
       $result = false;
       $is_followed = $this->isFollowed($followed, $follower);

       if(!$is_followed) {
           $result =true;
       }
       else {
           $sql = "DELETE FROM followers WHERE follower_id = :follower_id and  followed_id =:followed_id";
           $result = $this->query($sql, array('follower_id' => $follower, 'followed_id' => $followed));
       }
       return $result;
    }



    /**
     * get total count of followers
     *
     * @param int $userId
     * @return int
     */
    public function getFollowerCount($userId)
    {
        $result = $this->fetchRow("SELECT COUNT(*) AS num FROM followers WHERE follower_id = :follower_id", array('follower_id' => $userId));
        return $result['num'];
    }

    /**
     * returns users who follow user $userId
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getFollowers($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT u.* FROM users u, followers f WHERE u.id = f.follower_id AND followed_id = :followed_id LIMIT $start, $limit";
        $result = $this->fetchAll($query, array('followed_id' => $userId));
        return $result;
    }

    /**
     * get total count of followed
     * returns # of people who follow a $userId
     *
     * @param int $userId
     * @return int
     */
    public function getFollowedCount($userId)
    {
        $result = $this->fetchRow("SELECT COUNT(*) AS num FROM followers WHERE followed_id = :followed_id", array('followed_id' => $userId));
        return $result['num'];
    }

    /**
     * get followers set by user id
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getFolloweds($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT u.* FROM users u, followers f WHERE u.id = f.followed_id AND follower_id = :follower_id LIMIT $start, $limit";
        $result = $this->fetchAll($query, array('follower_id' => $userId));
        return $result;
    }

    /**
     * returns # of people who user $userId friends
     *
     * @param int $userId
     * @return int
     */
    public function getFriendThemCount($userId)
    {
        $result = $this->fetchRow("SELECT COUNT(*) AS num FROM friends WHERE from_user_id = :from_user_id", array('from_user_id' => $userId));
        return $result['num'];
    }

    /**
     * returns users who user $userId friended
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getFriendThems($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT u.* FROM users u, friends f WHERE u.id = f.to_user_id AND from_user_id = :from_user_id LIMIT $start, $limit";
        $result = $this->fetchAll($query, array('from_user_id' => $userId));
        return $result;
    }

    /**
     * returns # of people who have friended user $userId
     *
     * @param int $userId
     * @return int
     */
    public function getFriendMeCount($userId)
    {
        $result = $this->fetchRow("SELECT COUNT(*) AS num FROM friends WHERE to_user_id = :to_user_id", array('to_user_id' => $userId));
        return $result['num'];
    }

    /**
     * returns users who friended user $userId
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getFriendMes($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT u.* FROM users u, friends f WHERE u.id=f.from_user_id AND to_user_id = :to_user_id LIMIT $start, $limit";
        $result = $this->fetchAll($query, array('to_user_id' => $userId), 30);
        return $result;
    }

    /**
     * returns # of users who viewed user $userId's profile
     *
     * @param int $userId
     * @return int
     */
    public function getVisitorCount($userId)
    {
        $result = $this->fetchRow("SELECT COUNT(*) AS num FROM user_views_me WHERE my_user_id = :my_user_id", array('my_user_id' => $userId));
        return $result['num'];
    }

    /**
     * returns array of users who viewed user $userId's profile
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getVisitors($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT SQL_CALC_FOUND_ROWS u.*, YEAR(CURDATE())-YEAR(u.birthdate) AS age FROM users u, user_views_me uvm WHERE u.id = uvm.their_user_id AND uvm.my_user_id = :my_user_id LIMIT $start, $limit";
        $results = $this->fetchAll($query, array('my_user_id' => $userId), 30);
        $row = $this->fetchRow("SELECT FOUND_ROWS() AS count");
        $return['total_count'] = $row['count'];
        foreach ($results as &$user) {
          $this->expandUser($user, array('distance'=>1, 'city_state'=>1, 'reviews'=>1, 'blog'=>1));
        }
        $return['results'] = $results;
        return $return;
    }

    /**
     * returns # of people who user $userId viewed their profile
     *
     * @param int $userId
     * @return int
     */
    public function getVisitedUsersCount($userId)
    {
        $query = "SELECT COUNT(*) AS num FROM users u, user_views_other uvo WHERE u.id = uvo.their_user_id AND uvo.my_user_id = :my_user_id";
        $result = $this->fetchRow($query, array('my_user_id' => $userId));
        return $result['num'];
    }

    /**
     * returns array of users who viewed user $userId's profile
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getVisitedUsers($userId, $page, $limit = 20)
    {
        $start = $page ? ($page - 1) * $limit : 0;
        $query = "SELECT SQL_CALC_FOUND_ROWS u.* FROM users u, user_views_other uvo WHERE u.id = uvo.their_user_id AND uvo.my_user_id = :my_user_id LIMIT $start, $limit";
        $results = $this->fetchAll($query, array('my_user_id' => $userId));
        $row = $this->fetchRow("SELECT FOUND_ROWS() AS count");
        $return['total_count'] = $row['count'];
        foreach ($results as &$user) {
          $this->expandUser($user, array('avatar' => 'mini', 'city_state'=>1));
        }
        $return['results'] = $results;
        return $return;
    }

    /**
     * returns array of newly registered users
     *
     * @todo make it only pick users with approved photos
     *
     * @param int $limit
     * @return array
     */
    public function getNewUsers($limit = 20)
    {
        $result = $this->fetchAll("SELECT u.* FROM users u WHERE u.status = 'APPROVED' ORDER BY id DESC LIMIT 0, $limit");
        foreach ($result as &$user) {
          $this->expandUser($user, array('avatar' => 'mini', 'city_state'=>1));
        }
        return $result;
    }

    /********************* LISTS OF USERS functions ************************************************************/

    /**
     * get users who wrote most reviews in a region
     *
     * @param int $localId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getTopReviewersInRegion($localId, $page, $limit = 50)
    {
        $start = $page ? ($page - 1) * $limit : 0;

        if ($start+$limit<300) { // try stored cache
          $cacheName = 'reviewers.num_reviews';
          if ($localId) { $cacheName .= '.'.$localId; }
          $cache=$this->fetchRow("SELECT *, UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(updated_at) AS age FROM cache WHERE query=:query AND UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(updated_at)<seconds",array('query'=>$cacheName));

          if (isset($cache['id']) && $cache['result']!='') { // get a list of ids
            $ids=$cache['result'];
            $someIds = implode(',', array_slice(explode(',', $ids), $start, $limit));
            $query = "SELECT * FROM users WHERE id IN ($someIds) ORDER BY num_reviews DESC";
            #print "Q:$query"; return;
            $reviewers = $this->fetchAll($query);
            if (count($reviewers)>0) {
              return $reviewers;
            }
          }
        }

        $query = "SELECT * FROM users WHERE profile_on=1 AND status='APPROVED'";
        if ($localId) {
          $locals = $this->fetchRow("SELECT * FROM locals WHERE id = :id", array('id' => $localId));
          if (!empty($locals['parent_id_list'])) {
            $list = $locals['parent_id_list'];
            $query .= " AND local_id_list LIKE '${list}%' ";
          }
        }
        $query .= " ORDER BY num_reviews DESC LIMIT $start, $limit";
        #print "Q:$query"; return;
        $result = $this->fetchAll($query);
        return $result;
    }

    /**
     * get count of users who wrote most reviews in a region
     *
     * @param $localId $limit
     * @return int
     */
    public function getTopReviewersCountInRegion($localId = false)
    {
        $query = "SELECT COUNT(*) as reviewers_count FROM users WHERE 1";
        if ($localId) {
            $locals = $this->fetchRow("SELECT * FROM locals WHERE id= :id", array('id' => $localId));
            if (!empty($locals['parent_id_list'])) {
                $list = $locals['parent_id_list'];
                $query .= " AND local_id_list LIKE '${list}%' ";
            }
        }
        $result = $this->fetchAll($query);
        return $result[0]['reviewers_count'];
    }

    /**
     * get count of users in a region based on $type
     *
     * @param $localId $type(gender|user_type)
     * @return int
     */
    public function getTypeInRegion($localId, $type, $minTypeId=null)
    {
        $and = '';
        if ($minTypeId) { $and .= " type_id>$minTypeId "; }
        $result = $this->fetchAll("SELECT * FROM user_counts WHERE local_id=:id AND type=:type $and ORDER BY type_id",
                                  array('id' => $localId, 'type' => $type));

        if ($type == 'gender') { // temporary
            foreach ($result as &$row) {
                if ($row['type_id']==-1) { $row['name'] = 'All'; }
                elseif ($row['type_id']==0) { $row['name'] = 'Unknown'; }
                elseif ($row['type_id']==1) { $row['name'] = 'Male'; }
                elseif ($row['type_id']==2) { $row['name'] = 'Female'; }
                elseif ($row['type_id']==3) { $row['name'] = 'Couple'; }
                else { $row['name'] = 'Other'; }
            }
        }
        return $result;
    }

    /**************** User/Profile/Settings ACCESS functions ******************************/

    /**
     * get user id by login email
     *
     * @param string $email
     * @return false|int
     */
    public function getUserIdByLoginEmail($email)
    {
        $result = false;
        if (App_Model_Mail::isEmail($email)) {
            $sql = "SELECT users.id FROM users INNER JOIN " . $this->_login_table . " ON users.login_id = " . $this->_login_table . ".id WHERE " . $this->_login_table . ".email = :email";
            $result = $this->fetchOne($sql, array('email' => $email));
        }
        return $result;
    }

    /**
     * get user profile by user id or user email
     *
     * @todo may simplify ?
     * @param int|string $identity (userid|email)
     * @param false|array $params - reviews=0/1 (counts reviews)
     * @return array
     */
    public function getUser($identity, $params = false)
    {
        if (App_Model_Mail::isEmail($identity)) {
            $indentitySql = "email = :identity";
            //$indentitySql = "email = :identity AND email='$identity' ";
        } else {
            $indentitySql = "id = :identity";
        }
        $query = "SELECT *, IF (birthdate = '0000-00-00 00:00:00', 0, (YEAR(CURDATE())-YEAR(birthdate))) AS age, "
               . "IF (lastvisit IS NULL, 9999, DATEDIFF(CURDATE(),lastvisit)) AS lastvisit_days FROM users WHERE $indentitySql";
        $user = $this->fetchRow($query, array('identity' => $identity));

        if (isset($user['id'])) {
            $user = $this->expandUser($user, $params); // passed by reference - kim : please don't use reference
        }
        return $user;
    }

    /**
     * puts more information in a user array
     *
     * @todo - get photos num, get comments num
     *
     * @param array $user
     * @return array - user array
     */
    public function expandUser(&$user, $params = false) // please keep $user by reference
    {
        if (1 || isset($user)) {
            $user['display_username'] = self::getDisplayUsername($user);
            $user['genderName'] = '';
            if (! empty($user['gender'])) {
                if ($user['gender'] == 1) { $user['genderName'] = 'Male'; }
                if ($user['gender'] == 2) { $user['genderName'] = 'Female'; }
                if ($user['gender'] == 3) { $user['genderName'] = 'Couple'; }
                if ($user['gender'] == 4) { $user['genderName'] = 'Gay Couple'; }
                if ($user['gender'] == 5) { $user['genderName'] = 'Lesbian Couple'; }
                if ($user['gender'] == 6) { $user['genderName'] = 'Group'; }
            }

            if (isset($params['distance']) ||
                isset($params['city_state'])) {
              $geo = new App_Model_Geo();
            }

            if (isset($params['distance'])) {
              if ($myUser = $this->getStoredMyUser()) {
                $user['distance'] = $geo->getLatLonDistance($user['latitude'],$user['longitude'],
                                                            $myUser['latitude'],$myUser['longitude'],'miles');
              }
            }

            if (isset($params['city_state'])) { // improve look of city/state
              $user['city_state'] = $geo->getCityState($user['country'], $user['state'], $user['city']);
            }

            if (isset($params['reviews'])) {
                //$reviewModel = new App_Model_Review();
                //$user['num_reviews'] = $reviewModel->getReviewsCountByUserId($user['id']);
            }

            // count number of photos
            $user['num_photos'] = 0;
            if ($user['photo_id']) { // set this by default when they upload 1st photo OR when they mark a photo as their main
                $and = (self::isViewingOwn($user['id']) ? "AND (status!='DELETED')" : "AND status='APPROVED'");
                $userId = $user['id'];
                $row = $this->fetchRow("SELECT COUNT(*) AS num FROM user_photos WHERE user_id='$userId' $and");
                $user['num_photos'] = $row['num'];
            }

            // count number of compliments
            //$user['num_complements'] = $this->getUserCommentsCount($user['id']);

            // user blog
            if (isset($params['blog'])) {
                $blog = new App_Model_Blog();
                $user['num_questions'] = $blog->countUserQuestions($user['id']);
                //$user['num_responses'] = $blog->countUserResponses($user['id']);
            }

            //$user['is_followed'] = $this->isFollowed($user['id'], $this->getStoredMyUserId());

        }
        //$user['avatar']=$this->getAvatar($user['id]'); // maybe just use function in .phtml?
        return $user;
    }

    /**
     * give a user, defines ip_location and region info for them using their IP
     *
     * @param array $user
     * @return array
     */
    public function defineLocationFromIP($user = array())
    {
        $geo = new App_Model_Geo();
        $csc = $geo->getLocationbyIP();
        $user['ip_location'] = $csc;
        $myRegion = $geo->getLocalInfoFromCSC($csc['country'], $csc['state'], $csc['city']);
        $user['region'] = $myRegion;
        return $user;
    }

    /**
     * get user login email by login_id
     *
     * @param int $loginId
     * @return string
     */
    public function getLoginEmail($loginId)
    {
        $result = false;
        $loginId = (int) $loginId;
        if ($loginId) {
            $logins = $this->fetchCol("SELECT email FROM " . $this->_login_table . " WHERE id = :id", array('id' => $loginId));
            if (!empty($logins[0])) {
                $result = $logins[0];
            }
        }
        return $result;
    }

    /**
     * get user settings by user id
     *
     * @param int $userId
     * @return array - settings info
     */
    public function getSettings($userId)
    {
        $settings = $this->fetchRow("SELECT * FROM user_settings WHERE user_id = :user_id", array('user_id' => $userId));
        if (!$settings) {
	        $settings = array('no_photo_voting' => 0,
				  'no_photo_comments' => 0,
				  'browse_anonymously' => 0,
				  'filter_messages_not_ideal' => 0,
				  'filter_messages_not_friend' => 0
				  );
	    }
	    return $settings;
    }

    /**
     * returns # of user text to be reviewed
     *
     * @param string $type
     * @return int
     */
    public function getTextCount($type = 'pending')
    {
        $result = 0;
	    if ($type == 'pending') {
	        $count = $this->fetchRow("SELECT COUNT(*) AS num FROM user_text_pending WHERE 1");
	        $result = $count['num'];
	    }
	    return $result;
    }

    /**
     *
     * @param type $data
     * @return bool
     */
    public function update($data)
    {
        $result = $this->getTable()->update($data, array('id = ?' => (int)$data['id']));
        return !!$result;
    }

    /**
     * get user profile by user id
     *
     * @todo - seperate the "who views who" function
     *
     * @param int $userId
     * @return array - full profile for a user (e.g., including text)
     */
    public function getProfile($userId)
    {
        $user = array();

        if ($userId) {
            $user = $this->getUser($userId,array('distance'=>1,'city_state'=>1));
            $user['login_email'] = $this->getLoginEmail($user['login_id']);
            $user['favorite_types'] = $this->getFavoriteTypesByUserId($userId);
            $user['want_gender'] = explode(',', $user['want_genders']);

            // load all the profile-related text for this user
            $user['text'] = array();
            $texts = $this->fetchAll("SELECT * FROM user_text WHERE user_id = :user_id", array('user_id' => $userId));
            foreach ($texts as $text) {
                $user['text'][$text['name']] = $text['value'];
            }

            // if viewing your own profile, include pending text
            if (self::isViewingOwn($userId)) {
                // will check for denied text later
                if (false) {
                    $texts = $this->fetchAll("SELECT * FROM user_text_denied WHERE user_id = :user_id", array('user_id' => $userId));
                    foreach ($texts as $text) {
                        $user['text'][$text['name']] = $text['value'];
                    }
                }
                $texts = $this->fetchAll("SELECT * FROM user_text_pending WHERE user_id = :user_id", array('user_id' => $userId));
                foreach ($texts as $text) {
                    $user['text'][$text['name']] = $text['value'];
                }
            }

        }

        // check if a owner of places
        if (self::isViewingOwn($userId)) {
            $placeModel = new App_Model_Place();
            $user['num_places_owned'] = $placeModel->getOwnerCountByUserId($userId);
        } else {
            $user['num_places_owned'] = 0; // not used;
        }
        //save user view
        $this->recordViewedUser($userId);

        return $user;
    }

    public function recordViewedUser($userId) {
        $myUser = self::getStoredMyUser();
        if ($myUser && $userId) {

            $db = $this->getTable()->getAdapter();
            $myUserId = $myUser['id'];
            $my_settings = $this->getSettings($myUserId);

            // I'm not viewing my own profile, record who views who
            if(!self::isViewingOwn($userId)) {
                $db->query("INSERT INTO user_views_other (my_user_id, their_user_id, created_at) VALUES ('$myUserId', '$userId', NOW()) "
                     . "ON DUPLICATE KEY UPDATE created_at = NOW()");

                if (!$my_settings['browse_anonymously']) { // I'm not browsing anonymously
                   if(1) { // I'm allowed to show up in their view list
                        $db->query("INSERT INTO user_views_me (my_user_id,their_user_id,created_at) VALUES ('$userId','$myUserId',NOW()) "
                             . "ON DUPLICATE KEY UPDATE created_at = NOW()");
                   }
                }
            }
        }
    }


    /******************************** SEARCH functions  *************************************/

    /**
     * search users according to params
     *
     * @params parameter array
     * @return users array
     */
    public function searchUsers($params = array(), $page = 1, $limit = 30)
    {
        $start = $page ? (($page - 1) * $limit) : 0;

        $queryClause = ' 1 ';
        $joinClause = '';

        // handle parameters
        if (!empty($params)) {
            foreach ($params as $key => $value) {
	            // string escape
                // ABC : should use filters in form object
                if (is_string($value)) {
                    $value = mysql_escape_string($value);
                }

	            // for normal keys
	            if (!empty($value)) {
                    if ($key == 'city_state') {
                        list ($city, $state) = explode(', ', $value, 2);
                        if(!empty ($city)) { $queryClause .= " AND u.city ='$city' "; }
                        if(!empty($state)) { $queryClause .= " AND u.state='$state' "; }
                    }
	                $equals = " = '$value' ";
	                //if (preg_match('/\%/',$value)) { $equals = " LIKE '$value' "; }

                    // these fields can use wildcards
                    if ($key == 'user_name')            { $queryClause .= " AND u.username $equals "; }
                    elseif ($key == 'firstname')        { $queryClause .= " AND u.firstname $equals "; }
                    elseif ($key == 'zip')              { $queryClause .= " AND u.zip $equals "; }
                    elseif ($key == 'locals')           { $queryClause .= " AND u.local_id_list $equals "; }
                    elseif ($key == 'member_since')     { $queryClause .= " AND TO_DAYS(NOW()) - TO_DAYS(u.created_at) < '$value' "; }

                    // these two are trying to also address no birthday info for some members
                    elseif ($key == 'age_min')          { $queryClause .= " AND YEAR(CURDATE())-YEAR(u.birthdate) >= '$value' AND YEAR(CURDATE())-YEAR(u.birthdate)<100"; }
                    elseif ($key == 'age_max')          { $queryClause .= " AND YEAR(CURDATE())-YEAR(u.birthdate) <= '$value' AND YEAR(CURDATE())-YEAR(u.birthdate)>10"; }

                }
	        }
        }

        // could be multi genders
        if (!empty($params['gender'])) {
            $genders = implode(',', (array)$params['gender']);
            $queryClause .= " AND gender IN ($genders) ";
        }

        // region
        if (!empty($params['region'])) {
            $local_id = $params['region'];
            $local = $this->fetchRow("SELECT * FROM locals WHERE id='$local_id'");
            $local_id_list = $local['parent_id_list'];
            $queryClause .= " AND local_id_list LIKE '$local_id_list%' ";
        }

        // sub region
        if (!empty($params['sregion'])) {
            $local_id = $params['sregion'];
            $local = $this->fetchRow("SELECT * FROM locals WHERE id='$local_id'");
            $local_id_list = $local['parent_id_list'];
            $queryClause .= " AND local_id_list LIKE '$local_id_list%' ";
        }

        // for has_text_field with has_text_field_value
        if (!empty($params['has_text_field_value'])) {
            $joinClause = 'LEFT JOIN user_text ut ON (ut.user_id = u.id) ';
            $joinClause .= 'LEFT JOIN user_text_pending utp ON (utp.user_id = u.id) ';
            $name = $params['has_text_field'];
            $value = $params['has_text_field_value'];
            $queryClause .= " AND ((ut.name = '$name' AND ut.value LIKE '%{$value}%') OR (utp.name = '$name' AND utp.value LIKE '%{$value}%')) ";
        }

        // setup order by clause
        if (empty($params['sort_by']) || $params['sort_by']=='created_at') { $orderBy='u.created_at'; }
        elseif ($params['sort_by']=='username')   { $orderBy='u.username'; }
        elseif ($params['sort_by']=='id')         { $orderBy='u.id'; }

        $query = "SELECT SQL_CALC_FOUND_ROWS u.*, YEAR(CURDATE())-YEAR(u.birthdate) AS age FROM users AS u $joinClause "
               . "WHERE $queryClause ORDER BY $orderBy DESC LIMIT $start,$limit";
        //print "Q:$query<br>";

        $results = $this->fetchAll($query);

        $row = $this->fetchRow("SELECT FOUND_ROWS() AS count");
        $return['total_count'] = $row['count'];

        foreach ($results as &$user) {
          $this->expandUser($user, array('avatar' => 'mini', 'city_state'=>1));
        }

        $return['results'] = $results;
        return $return;
    }

    /**
     * get similar users count
     *
     * @todo - need andrew to tell how to count
     *
     * @param int $userId
     * @return int
     */
    public function getSimilarCountByUserId($userId)
    {
        return 0;
    }

    /**
     * returns array of users similar to the given userId
     * @todo - will improve later
     */
    public function getSimilarUsers($userId, $page = 0, $limit = 10)
    {
        $start = $page ? (($page - 1) * $limit) : 0;

        // test to see if it's recently done
        $similar = $this->fetchRow("SELECT * FROM user_similar WHERE user_id = :user_id AND updated_at > NOW() - INTERVAL 1 SECOND", array('user_id' => $userId));
        if ($similar['user_id']) {
            $ids = $similar['user_ids'];
            $query = "SELECT *, IF(photo_id, 1, 0) AS has_photo FROM users WHERE id IN ($ids) AND profile_on=1 "
                   . "ORDER BY has_photo DESC, num_reviews DESC, created_at DESC LIMIT $start, $limit";
            //print "Q2:$query<br>";
            $users = $this->fetchAll($query);
        } else {
            $user = $this->getUser($userId);
            $localIdList = $user['local_id_list'];
            $localId = $user['local_id'];

            $cond = " profile_on = 1 AND id != '$userId' ";

            if (0) { // $site!='dine') {
                $gender = $user['gender'];
                if ($gender) { $cond.=" AND gender='$gender' "; }
            }

            $age = $user['age'];
            $min_age=15;
            $max_age=100;
            if ($age>=18 && $age<100) {
                $min_age=floor(.8*$age);
                if ($min_age<18) { $min_age=18; }
                $max_age=floor(1.2*$age);
                $cond .= " AND YEAR(CURDATE()) - YEAR(birthdate) >= $min_age AND YEAR(CURDATE()) - YEAR(birthdate) <= $max_age ";
            }

            // similar = near same age, same gender, and similar region
            $localSQL = $localIdList ? "AND local_id_list='$localIdList'" : '';
            $query = "SELECT *, IF(photo_id, 1, 0) AS has_photo FROM users WHERE $cond $localSQL "
                   . "ORDER BY has_photo DESC, num_reviews DESC, created_at DESC LIMIT $start, $limit";
            //print "Q:$query";
            $users = $this->fetchAll($query);

            if (count($users) == 0) { // try again but in a wider region
                $localIdList = preg_replace('/\d+,$/','',$localIdList); // remove last "number," from $localIdList
                $localSQL = $localIdList ? " AND local_id_list LIKE '$localIdList%' " : '';
                $query = "SELECT *, IF(photo_id,1,0) AS has_photo FROM users WHERE $cond $localSQL ORDER BY has_photo DESC, created_at DESC LIMIT $start, $limit";
                $users = $this->fetchAll($query);
            }

            if ($userId and (count($users) > 0)) {
                $ids = '';
                foreach ($users as &$user) {
                    $ids .= $user['id'].',';
                }
                $ids = chop($ids,',');
                $db = $this->getTable()->getAdapter();
                $db->query("INSERT INTO user_similar (user_id, user_ids, updated_at) VALUES($userId, '$ids', NOW()) "
                    . "ON DUPLICATE KEY UPDATE updated_at = NOW(), user_ids = '$ids'");
            }
        }

        return $users;
    }

    /**
     * add user comment
     *
     * @param array $data
     * @return boolean|int
     */
    public function addUserComments($data)
    {
    	$params = array();
    	$params['from_user_id'] = $data['from_user_id'];
    	$params['to_user_id'] = $data['to_user_id'];
    	$params['comment'] = $data['comment'];

    	$sql = "INSERT INTO user_comments (from_user_id, to_user_id, comment, status, created_at) VALUES (:from_user_id, :to_user_id, :comment, 'PENDING', NOW())";
    	return $this->query($sql, $params);
    }

    /**
     * get user's comments count
     *
     * @param int $userId
     * @return false|int
     */
    public function getUserCommentsCount($userId)
    {
        $result = false;
        if (self::isViewingOwn($userId)) {
            $sql = "SELECT COUNT(*) AS count FROM user_comments WHERE (to_user_id = :to_user_id AND status = 'APPROVED')";
        } else {
            $fromUserId = App_Model_User::getStoredMyUserId();
            $sql = "SELECT COUNT(*) AS count FROM user_comments WHERE (to_user_id = :to_user_id AND status = 'APPROVED') OR (from_user_id = '$fromUserId' AND status != 'DELETED')";
        }
        if ($row = $this->fetchRow($sql, array('to_user_id' => $userId))) {
            $result = $row['count'];
        }
        return $result;
    }

    /**
     * get user's comments list
     *
     * @param int $userId
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getUserComments($userId, $page = 1, $limit = 10)
    {
        $start = $page ? (($page - 1) * $limit) : 0;
        if (self::isViewingOwn($userId)) {
            $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM user_comments WHERE (to_user_id = :to_user_id AND status = 'APPROVED') "
                   . "ORDER BY created_at DESC LIMIT $start, $limit";
        } else {
            $fromUserId = App_Model_User::getStoredMyUserId();
            $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM user_comments WHERE (to_user_id = :to_user_id AND status = 'APPROVED') OR (from_user_id = '$fromUserId' AND status != 'DELETED') "
                   . "ORDER BY created_at DESC LIMIT $start, $limit";
        }
        $results = $this->fetchAll($sql, array('to_user_id' => $userId));
        $row = $this->fetchRow("SELECT FOUND_ROWS() AS count");
        $return['total_count'] = $row['count'];
        $return['results'] = $results;
        return $return;
    }

    /*************************** ADMIN functions ************************************/

    /**
     * admin tool - return array of user_text_pending
     */
    public function getUserPendingText($page = 1, $limit = 10)
    {
        $start = $page ? (($page - 1) * $limit) : 0;
        $sql = "SELECT SQL_CALC_FOUND_ROWS * FROM user_text_pending ORDER BY created_at LIMIT $start,$limit";
        $results = $this->fetchAll($sql);

        $userModel = new App_Model_User();
        foreach ($results as &$r) {
            $r['user'] = $userModel->getUser($r['user_id']);
            $r['text_info'] = App_Filter_PreReviewText::filter($r['value']);
            $r['value'] = App_Filter_Textarea::filter($r['value']);
        }

        $return['results'] = $results;

        $row = $this->fetchRow("SELECT FOUND_ROWS() AS count");
        $return['total_count'] = $row['count'];
        return $return;
    }

    /** review user pending text **/
    public function reviewUserPendingText($params)
    {
        $actions = $params['actions'];
        $seconds = $params['seconds'];

    	$db = $this->getTable()->getAdapter();
    	$admin = App_Model_Admin_Administrator::getStoredMyAdmin();
    	$adminId = $admin['id'];

    	$countApprove = 0;
    	$countDeny = 0;
    	foreach ($actions as $id => $data) {
    		if ($data['action'] == 'approve') {
    			$sql = "SELECT * FROM user_text_pending where id = $id";
    			$result = $this->fetchRow($sql);

    			$sql = "INSERT INTO user_text SET user_id = :user_id, name = :name, value = :value, created_at = :created_at, reviewer_id = :reviewer_id, review_time = now()";

                $db->query($sql, array('user_id'=>$result['user_id'],
                                       'name'=>$result['name'],
                                       'value'=>$data['text'],
                                       'created_at'=>$result['created_at'],
                                       'reviewer_id'=>$adminId));

                $db->query("DELETE FROM user_text_pending WHERE id = $id");

    			$countApprove++;
    		} else if ($data['action'] == 'deny') {
              $db->query("DELETE FROM user_text_pending WHERE id = $id");
    			$countDeny++;
    		}
    	}

    	// save approve/deny counts in Aministrator::logAdminWork function
        $admin = new App_Model_Admin_Administrator();
        $adminLog = array('type'=>'user_text', 'seconds'=>$seconds, 'counts'=>array('approve'=>$countApprove, 'deny'=>$countDeny));
        $admin->logAdminWork($adminLog);
        return true;
    }
}
